package com.mapforge.gui;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.swing.JOptionPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.mapforge.listener.RenderEvent;
import com.mapforge.listener.RenderListener;
import com.mapforge.listener.TileLayerCreationEvent;
import com.mapforge.listener.TileLayerCreationListener;
import com.mapforge.listener.TileLayerDeletionEvent;
import com.mapforge.listener.TileLayerDeletionListener;
import com.mapforge.listener.TileLayerEditListener;
import com.mapforge.listener.TileLayerSelectionEvent;
import com.mapforge.listener.TileLayerSelectionListener;
import com.mapforge.listener.TileMapCreationEvent;
import com.mapforge.listener.TileMapCreationListener;
import com.mapforge.listener.TileSelectionListener;
import com.mapforge.map.TileLayer;
import com.mapforge.map.TileMap;

/**
 * The {@link TileLayerTransferableContainer} class holds a list of
 * {@link TransferablePanel} used to represent {@link TileLayer TileLayers.} It
 * also handles registering {@link TileLayerEditListener TileLayerListeners}
 * with all {@link TileLayerTransferablePanel TileLayerTransferablePanels} and
 * {@link TileLayerDeletionListener TileLayerDeletionListeners} for changes
 * occurring.
 * 
 * @author Will Morrison
 * 
 */
public class TileLayerTransferableContainer extends TransferableContainer
		implements TileLayerCreationListener, TileMapCreationListener {

	/**
	 * Default serial version ID, here to remove warnings.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * List of registered listeners interested in tile layer deletions.
	 */
	private transient List<TileLayerDeletionListener> tilelayerDeletionListeners;
	/**
	 * Registered {@link TileLayerEditListener TileLayerEditListeners}
	 */
	private transient List<TileLayerEditListener> tileLayerEditListeners;
	/**
	 * Registered {@link TileLayerSelectionListener TileLayerSelectionListeners}
	 */
	private transient List<TileLayerSelectionListener> tileLayerSelectionListeners;
	/**
	 * Registered {@link RenderListener render listeners}
	 */
	private transient List<RenderListener> renderListeners;

	private KeyListener deleteKeyListener;

	private TileMap tileMap;

	public TileLayerTransferableContainer() {
		super();
		this.setFocusable(true);

		this.tileMap = null;
		// Lists of various listeners
		this.tilelayerDeletionListeners = new ArrayList<TileLayerDeletionListener>();
		this.tileLayerEditListeners = new ArrayList<TileLayerEditListener>();
		this.tileLayerSelectionListeners = new ArrayList<TileLayerSelectionListener>();
		this.renderListeners = new ArrayList<RenderListener>();

		ListSelectionListener listSelectionListener = new TileLayerListSelectionListener();
		this.deleteKeyListener = new TileLayerKeyListener();
		Dimension size = new Dimension(200, 200);

		this.setMinimumSize(size);
		this.addListSelectionListener(listSelectionListener);
		this.addKeyListener(deleteKeyListener);
	}

	/**
	 * Registers a {@link TileLayerEditListener} with all
	 * {@link TileLayerTransferablePanel} within this container. Whenever a tile
	 * layer change is performed on one of these panels, an event will be sent
	 * to all registered listeners.
	 * 
	 * @param listener
	 *            listener to register with all
	 *            {@link TileLayerTransferablePanel}
	 */
	public void addTileLayerEditListener(TileLayerEditListener listener) {
		Iterator<TransferablePanel> it = getAllPanels();
		TransferablePanel transferablePanel;
		while (it.hasNext()) {
			transferablePanel = it.next();
			if (transferablePanel instanceof TileLayerTransferablePanel) {
				TileLayerTransferablePanel panel = (TileLayerTransferablePanel) transferablePanel;
				panel.addTileLayerEditListener(listener);
			}
		}
		this.tileLayerEditListeners.add(listener);
	}

	@Override
	protected void addImpl(Component component, Object constraints, int index) {
		if (component instanceof TileLayerTransferablePanel) {
			super.addImpl(component, constraints, index);
		} else {
			throw new IllegalArgumentException(
					"Can only add TileLayerTransferablePanels to a transferable container");
		}
	}

	public boolean addRenderListener(RenderListener listener) {
		return this.renderListeners.add(listener);
	}

	public void clearRenderListeners() {
		this.renderListeners.clear();
	}

	public boolean removeRenderListener(RenderListener listener) {
		return this.renderListeners.remove(listener);
	}

	/**
	 * Registers a {@link TileLayerDeletionListener} with this
	 * {@link TileLayerTransferableContainer TileLayerTransferableContainer.}
	 * Unless the listener is unregistered, it will be informed of any tile
	 * layer deletions performed by this instance.
	 * 
	 * @see #removeTileLayerDeletionListener(TileLayerDeletionListener)
	 * @param listener
	 *            specified {@link TileLayerDeletionListener} to be registered
	 * @return whether the specified listener was successfully registered
	 */
	public boolean addTileLayerDeletionListener(
			TileLayerDeletionListener listener) {
		return this.tilelayerDeletionListeners.add(listener);
	}

	/**
	 * Unregisters the specified {@link TileLayerDeletionListener} from this
	 * {@link TileLayerTransferableContainer TileLayerTransferableContainer.}
	 * Once unregistered the listener will no longer be informed of tile layer
	 * deletions performed by this instance.
	 * <p>
	 * Note this function will return false if the specified
	 * {@link TileLayerDeletionListener} to be unregistered was never registered
	 * with this instance.
	 * </p>
	 * 
	 * @see #addTileLayerDeletionListener(TileLayerDeletionListener)
	 * @param listener
	 *            specified {@link TileLayerDeletionListener} to be unregistered
	 * @return whether the specified listener was successfully unregistered
	 */
	public boolean removeTileLayerDeletionListener(
			TileLayerDeletionListener listener) {
		return this.tilelayerDeletionListeners.remove(listener);
	}

	/**
	 * Registers the specified {@link TileLayerSelectionListener} with this
	 * {@link TileLayerTransferableContainer TileLayerTransferableContainer.}
	 * Once registered the listener will be informed of any
	 * {@link TileLayerSelectionEvent TileLayerSelectionEvents} this instance
	 * creates.
	 * 
	 * @see #removeTileLayerSelectionListener(TileLayerSelectionListener)
	 * @param listener
	 *            specified {@link TileLayerSelectionListener} to be registered
	 * @return whether the specified {@link TileLayerSelectionListener} was
	 *         successfully registered
	 */
	public boolean addTileLayerSelectionListener(
			TileLayerSelectionListener listener) {
		return this.tileLayerSelectionListeners.add(listener);
	}

	/**
	 * Unregisters the specified {@link TileSelectionListener} with this
	 * {@link TileLayerTransferableContainer TileLayerTransferableContainer.}
	 * Once unregistered the listener will not be informed of any
	 * {@link TileLayerSelectionEvent TileLayerSelectionEvents} this instance
	 * creates.
	 * <p>
	 * Note this function will return false if the specified
	 * {@link TileLayerDeletionListener} to be unregistered was never registered
	 * with this instance.
	 * </p>
	 * 
	 * @see #addTileLayerSelectionListener(TileLayerSelectionListener)
	 * @param listener
	 *            specified {@link TileLayerSelectionListener} to be
	 *            unregistered
	 * @return whether the specified {@link TileLayerSelectionListener} was
	 *         successfully unregistered
	 */
	public boolean removeTileLayerSelectionListener(
			TileLayerSelectionListener listener) {
		return this.tileLayerSelectionListeners.remove(listener);
	}

	protected void fireTileLayerDeletionEvent(TileLayerDeletionEvent evt) {
		for (int i = 0; i < tilelayerDeletionListeners.size(); i++) {
			this.tilelayerDeletionListeners.get(i).tileLayerDeletionPerformed(
					evt);
		}
	}

	protected void fireRenderEvent() {
		RenderEvent renderEvent = new RenderEvent(this);
		for (int i = 0; i < renderListeners.size(); i++) {
			this.renderListeners.get(i).renderEventOccurred(renderEvent);
		}
	}

	@Override
	public void endDragAndDrop() {
		Iterator<TransferablePanel> it = this.getAllPanels();
		TileLayerTransferablePanel panel;
		int z = 0;
		while (it.hasNext()) {
			panel = (TileLayerTransferablePanel) it.next();
			panel.getTileLayer().setZ(z);
			z++;
		}
		tileMap.sortLayers();
		fireRenderEvent();
		// TODO fire tile layer edit for the resort
	}

	public void tileLayerCreationPerformed(TileLayerCreationEvent tileLayerEvent) {
		TileLayer layer = tileLayerEvent.getCreatedTileLayer();
		TileLayerTransferablePanel panel = new TileLayerTransferablePanel(layer);
		// needs to be focusable to receive key events
		panel.setFocusable(true);
		// register new panel with all known tile layer edit listeners
		for (TileLayerEditListener listener : tileLayerEditListeners) {
			panel.addTileLayerEditListener(listener);
		}
		panel.addKeyListener(deleteKeyListener);
		this.addTransferablePanel(panel);
		revalidate();
	}

	private class TileLayerKeyListener extends KeyAdapter {

		@Override
		public void keyReleased(KeyEvent e) {
			int keycode = e.getKeyCode();
			if (keycode == KeyEvent.VK_DELETE
					|| keycode == KeyEvent.VK_BACK_SPACE) {
				Iterator<TransferablePanel> it = getSelectedPanels();
				// no selected panels
				if (!it.hasNext()) {
					JOptionPane.showMessageDialog(null, "No layers selected",
							"Layer Deletion", JOptionPane.INFORMATION_MESSAGE);
				}
				// There was at least one selected panel.
				else {
					String msg = "Are you sure you want to delete the selected layer";
					msg += (getNumberSelectedPanels() > 1 ? "s?" : "?");
					int opt = JOptionPane.showConfirmDialog(null, msg,
							"Layer Deletion", JOptionPane.YES_NO_OPTION);
					if (opt == JOptionPane.YES_OPTION) {
						TileLayerTransferablePanel tileLayerPanel;
						while (it.hasNext()) {
							TransferablePanel panel = it.next();
							Set<TileLayer> hashset = new HashSet<TileLayer>();
							if (panel instanceof TileLayerTransferablePanel) {
								tileLayerPanel = (TileLayerTransferablePanel) panel;
								hashset.add(tileLayerPanel.getTileLayer());
								it.remove();
								remove(tileLayerPanel);
							}
							TileLayerDeletionEvent evt = new TileLayerDeletionEvent(
									TileLayerTransferableContainer.this,
									hashset);
							fireTileLayerDeletionEvent(evt);
							fireRenderEvent();
						}
						revalidate();
					}
				}
			}
		}
	}

	@Override
	public void remove(Component c) {
		super.remove(c);
		this.repaint();
	}

	@Override
	public void tileMapCreationPerformed(TileMapCreationEvent mapCreationEvent) {
		this.clearTransferablePanels();
		this.tileMap = mapCreationEvent.getTileMapCreated();
	}

	protected void fireTileLayerSelectionEvent(TileLayerSelectionEvent event) {
		for (int i = 0; i < tileLayerSelectionListeners.size(); i++) {
			tileLayerSelectionListeners.get(i).tileLayerSelectionPerformed(
					event);
		}
	}

	/**
	 * Recieves {@link ListSelectionEvent ListSelectionEvents} and converts them
	 * to a {@link TileLayerSelectionEvent}
	 * 
	 * @author Will Morrison
	 * 
	 */
	private class TileLayerListSelectionListener implements
			ListSelectionListener {

		@Override
		public void valueChanged(ListSelectionEvent e) {
			Object obj = e.getSource();
			if (obj instanceof TileLayerTransferableContainer) {
				TileLayerTransferableContainer con = (TileLayerTransferableContainer) obj;
				Set<TileLayer> hashset = new HashSet<TileLayer>();
				Iterator<TransferablePanel> it = con.getSelectedPanels();
				TransferablePanel p;
				while (it.hasNext()) {
					p = it.next();
					if (p instanceof TileLayerTransferablePanel) {
						hashset.add(((TileLayerTransferablePanel) p)
								.getTileLayer());
					}
				}
				fireTileLayerSelectionEvent(new TileLayerSelectionEvent(
						TileLayerTransferableContainer.this, hashset));
			}
		}

	}
}
